package com.zcah.mask.service.impl;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.zcah.common.util.FileUtil;
import com.zcah.db.DbSource;
import com.zcah.db.dao.DbsourceDao;
import com.zcah.db.dbsource.DbSourceFactory;
import com.zcah.db.domain.ColumnProperty;
import com.zcah.db.domain.DbsourceDO;
import com.zcah.db.domain.MaskRuleDO;
import com.zcah.file.dao.FileDao;
import com.zcah.file.domain.FileDO;
import com.zcah.mask.ResultSetSpliterator;
import com.zcah.mask.core.MaskMethod;
import com.zcah.mask.core.MaskTask;
import com.zcah.mask.dao.MaskJobDao;
import com.zcah.mask.domain.MaskJobDO;
import com.zcah.mask.service.MaskService;
import com.zcah.mask.vo.MaskVO;
import com.zcah.mask.vo.TableProperty;

@Service
public class MaskServiceImpl implements MaskService {

	private static final Logger logger = LoggerFactory.getLogger(MaskServiceImpl.class);

	@Autowired
	private DbsourceDao dbsourceDao;

	@Autowired
	private MaskJobDao maskJobDao;

	@Autowired
	private FileDao fileDao;

	@Value("${zcahmask.result.path}")
	private String resultPath;

	private String dbDelimeter = ",";
	private String dbEncode = "utf8";
	private String dbLineDelimeter = "\n";

	public static String getDate() {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
		return sdf.format(new Date());
	}

	private MaskJobDO preMask() {
		MaskJobDO maskJobDO = new MaskJobDO();
		maskJobDO.setJobCreatetime(new Date());
		// TODO 从权限管理模块获取用户
		maskJobDO.setJobCreateuser("test");
		return maskJobDO;
	}

	@Override
	public void startDbMaskJob(MaskVO dbMaskVO) {
		MaskJobDO maskJobDO = preMask();
		DbsourceDO dbsourceDO = dbsourceDao.get(dbMaskVO.getSourceId());

		String jobResultPath = resultPath + dbsourceDO.getName() + "-" + getDate() + ".zip";
		maskJobDO.setJobResultpath(jobResultPath);
		maskJobDO.setJobName(dbsourceDO.getName() + "_" + getDate());
		maskJobDO.setDatasourceName(dbsourceDO.getName());

		maskJobDao.save(maskJobDO);
		new Thread(() -> dbMask(dbsourceDO, dbMaskVO.getTableProperties(), jobResultPath, maskJobDO)).start();
	}

	@Override
	public void startFileMaskJob(MaskVO fileMaskVO) {
		MaskJobDO maskJobDO = preMask();
		FileDO maskFile = fileDao.get(fileMaskVO.getSourceId());
		File file = new File(resultPath + maskFile.getFileName() + "-" + getDate());
		file.mkdirs();

		String jobResultpath = file.getAbsolutePath();

		maskJobDO.setJobName(maskFile.getCategoryName() + "-" + maskFile.getFileName());
		maskJobDO.setDatasourceName(maskFile.getFileName());
		maskJobDO.setJobResultpath(jobResultpath);
		maskJobDao.save(maskJobDO);
		new Thread(() -> textMask(maskFile, fileMaskVO.getTableProperties(), jobResultpath, maskJobDO)).start();
	}

	// 从resultset获取一行数据
	private String[] getRow(ResultSet resultSet, int columnCount) {
		String[] temp = new String[columnCount];
		try {
			for (int i = 1; i <= columnCount; i++) {
				temp[i - 1] = resultSet.getString(i);
			}
			return temp;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	// 对该行数据进行脱敏
	private void rowMask(String[] str, int columnCount, List<ColumnProperty> columnPropertyList) {
		MaskTask task = new MaskTask();
		for (int i = 0; i < columnCount; i++) {
			MaskRuleDO maskRuleDO = columnPropertyList.get(i).getMaskRuleDO();
			if (maskRuleDO != null) {
				task.setData(str[i]);
				task.setRuleName(maskRuleDO.getMaskFunction());
				task.setBeginNum(maskRuleDO.getMaskStart());
				task.setSize(maskRuleDO.getMaskLen());
				str[i] = String.format("\"%s\"", task.maskRuleTask());
			} else {
				str[i] = String.format("\"%s\"", str[i]);
			}
		}
	}

	private boolean dbMask(DbsourceDO dbsourceDO, List<TableProperty> tablePropertyList, String destPath,
			MaskJobDO maskJobDO) {
		long startTime = System.currentTimeMillis();

		try (ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(destPath)),
				Charset.forName(dbEncode)); DbSource dbSource = DbSourceFactory.getInstance(dbsourceDO);) {
			for (TableProperty tableProperty : tablePropertyList) {
				List<ColumnProperty> columnPropertyList = tableProperty.getColumnPropertyList();

				zos.putNextEntry(new ZipEntry(tableProperty.getTableName() + ".csv"));
				ResultSet resultSet = dbSource.getResultSet(tableProperty.getTableName());

				int columnCount = columnPropertyList.size();
				String[] row = new String[columnCount];
				ResultSetMetaData metaData = resultSet.getMetaData();
				for (int i = 0; i < columnCount; i++) {
					row[i] = String.format("\"%s\"", metaData.getColumnName(i + 1));
				}
				zos.write((String.join(dbDelimeter, row) + dbLineDelimeter).getBytes());

				new ResultSetSpliterator<>(resultSet, r -> getRow(r, columnCount)).stream().parallel().forEach(o -> {
					try {
						rowMask(o, columnCount, columnPropertyList);
						zos.write((String.join(dbDelimeter, o) + dbLineDelimeter).getBytes(dbEncode));
					} catch (IOException e) {
						throw new RuntimeException(e);
					}
				});
				zos.closeEntry();
			}

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			File file = new File(destPath);
			if (file.exists() && file.isFile()) {
				file.delete();
			}
			maskJobDO.setJobFinshtime((System.currentTimeMillis() - startTime) / 1000);
			maskJobDO.setJobResult("failure");
			maskJobDao.update(maskJobDO);
			return false;
		}
		maskJobDO.setJobFinshtime((System.currentTimeMillis() - startTime) / 1000);
		maskJobDO.setJobResult("success");
		maskJobDao.update(maskJobDO);
		return true;
	}

	// 不压缩txt和csv脱敏
	private void textMask(FileDO maskFile, List<TableProperty> tablePropertyList, String jobResultpath,
			MaskJobDO maskJobDO) {
		logger.info("Start Mask");
		long startTime = System.currentTimeMillis();

		String charsetName = maskFile.getFileEncode();
		String delimiter = maskFile.getFileDelimeter();
		String textQualifier = maskFile.getTextQualifier();
		Integer fileFirstline = maskFile.getFileFirstline();
		String recordDelimeter;
		switch (maskFile.getRecordDelimeter()) {
		case "CRLF":
			recordDelimeter = "\r\n";
			break;
		case "LF":
			recordDelimeter = "\n";
			break;
		case "CR":
			recordDelimeter = "\r";
			break;
		default:
			recordDelimeter = "\n";
			break;
		}

		List<String> readPathList = new ArrayList<>();
		File file = new File(maskFile.getFilePath());
		if (file.isDirectory()) {
			for (File f : FileUtil.getFileList(file)) {
				for (TableProperty t : tablePropertyList) {
					if (f.getName().equals(t.getTableName())) {
						readPathList.add(f.getAbsolutePath());
					}
				}
			}
		} else {
			readPathList.add(maskFile.getFilePath());
		}
		logger.info("readList:" + readPathList);
		for (TableProperty tableProperty : tablePropertyList) {
			String fileName = "";
			String readPath = "";
			for (String filePath : readPathList) {
				fileName = filePath.substring(filePath.lastIndexOf(File.separator) + 1);
				if (fileName.equals(tableProperty.getTableName())) {
					readPath = filePath;
					break;
				}
			}
			String writePath = jobResultpath + File.separator + fileName;
			logger.info("read:" + readPath + "\nwrite:" + writePath);
			try (BufferedWriter bufferedWriter = new BufferedWriter(
					new OutputStreamWriter(new FileOutputStream(writePath), charsetName), 5 * 1024 * 1024)) {
				List<ColumnProperty> columnPropertyList = tableProperty.getColumnPropertyList();
				Stream<String> lines = Files.lines(Paths.get(readPath), Charset.forName(charsetName));
				final int[] firstLine = { 0 };
				lines.parallel().forEach(line -> {
					if (fileFirstline == 1 && firstLine[0] == 0) {
						try {
							bufferedWriter.write(line + recordDelimeter);
							firstLine[0]++;
						} catch (IOException e) {
							logger.error(e.getLocalizedMessage(), e);
						}
					} else {
						String regex = textQualifier + delimiter + textQualifier;
						if (textQualifier.length() == 1) {
							line = line.substring(1, line.lastIndexOf(textQualifier));
						}
						String[] content = line.split(regex);
						List<String> replaced = new ArrayList<>();
						MaskMethod maskMethod = new MaskMethod();
						for (int i = 0; i < content.length; i++) {
							MaskRuleDO maskRuleDO = columnPropertyList.get(i).getMaskRuleDO();
							if (maskRuleDO != null) {
								switch (maskRuleDO.getMaskFunction()) {
								case "MD5Encrypt":
									replaced.add(maskMethod.MD5Encrypt(content[i], maskRuleDO.getMaskStart(),
											maskRuleDO.getMaskLen()));
									break;
								case "hideMask":
									replaced.add(maskMethod.hideMask(content[i], maskRuleDO.getMaskStart(),
											maskRuleDO.getMaskLen()));
									break;
								case "NULLCutMask":
									replaced.add(maskMethod.NULLCutMask(content[i], maskRuleDO.getMaskStart(),
											maskRuleDO.getMaskLen()));
									break;
								case "replaceMask":
									replaced.add(maskMethod.replaceMask(content[i], maskRuleDO.getMaskStart(),
											maskRuleDO.getMaskLen()));
									break;
								case "shuffleMask":
									replaced.add(maskMethod.shuffleMask(content[i], maskRuleDO.getMaskStart(),
											maskRuleDO.getMaskLen()));
									break;
								case "phoneMask":
									replaced.add(maskMethod.phoneMask(content[i], maskRuleDO.getMaskStart(),
											maskRuleDO.getMaskLen()));
									break;
								case "dateConversion":
									replaced.add(maskMethod.dateConversion(content[i], maskRuleDO.getMaskStart(),
											maskRuleDO.getMaskLen()));
									break;
								case "dateInteger":
									replaced.add(maskMethod.dateInteger(content[i], maskRuleDO.getMaskStart(),
											maskRuleDO.getMaskLen()));
									break;
								case "addressMask":
									replaced.add(maskMethod.addressMask(content[i], maskRuleDO.getMaskStart(),
											maskRuleDO.getMaskLen()));
									break;
								default:
									break;
								}
							} else {
								replaced.add(content[i]);
							}
						}
						try {
							if (textQualifier.equals("\"") || textQualifier.equals("'")) {
								bufferedWriter.write(
										textQualifier + String.join(regex, replaced) + textQualifier + recordDelimeter);
							} else {
								bufferedWriter.write(String.join(regex, replaced) + recordDelimeter);
							}
						} catch (IOException e) {
							logger.error(e.getLocalizedMessage(), e);
						}
					}

				});
				lines.close();
			} catch (IOException e) {
				maskJobDO.setJobFinshtime((System.currentTimeMillis() - startTime) / 1000);
				maskJobDO.setJobResult("failure");
				maskJobDao.update(maskJobDO);
				logger.error(e.getLocalizedMessage(), e);
				return;
			}
		}
		maskJobDO.setJobFinshtime((System.currentTimeMillis() - startTime) / 1000);
		maskJobDO.setJobResult("success");
		maskJobDao.update(maskJobDO);
	}
}
